/*
*Copyright 2021-2023 NERCIS
*
*Licensed under the Apache License, Version 2.0 (the "License");
*you may not use this file except in compliance with the License.
*You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*Unless required by applicable law or agreed to in writing, software
*distributed under the License is distributed on an "AS IS" BASIS,
*WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*See the License for the specific language governing permissions and
*limitations under the License.
*/

package cn.ac.nercis.pes.community.controller.evaluation.analyze.analysis;

import cn.ac.nercis.pes.common.constant.common.BaseConstants;
import cn.ac.nercis.pes.common.constant.evaluation.RiskLevel;
import cn.ac.nercis.pes.community.component.controls.CustomScrollPane;
import cn.ac.nercis.pes.community.component.controls.MenuListCell;
import cn.ac.nercis.pes.community.component.dialog.MessageDialog;
import cn.ac.nercis.pes.community.component.dialog.MessageType;
import cn.ac.nercis.pes.community.component.dialog.PageDialog;
import cn.ac.nercis.pes.community.constant.AppConstants;
import cn.ac.nercis.pes.community.controller.base.GlobalEventHandle;
import cn.ac.nercis.pes.community.controller.dialog.UnitRecordController;
import cn.ac.nercis.pes.community.controller.evaluation.analyze.RiskAnalysisController;
import cn.ac.nercis.pes.community.manager.RealTimeSaveManager;
import cn.ac.nercis.pes.community.model.vo.MenuVO;
import cn.ac.nercis.pes.community.utils.ControlUtils;
import cn.ac.nercis.pes.community.utils.*;
import cn.ac.nercis.pes.community.views.dialog.UnitRecordView;
import cn.ac.nercis.pes.community.views.evaluation.analyze.RiskAnalysisView;
import cn.ac.nercis.pes.model.dto.evaluation.analyze.ProblemMeasureDTO;
import cn.ac.nercis.pes.model.dto.evaluation.analyze.UnitRiskAnalysisDTO;
import cn.ac.nercis.pes.model.dto.evaluation.prepare.SecurityThreatDTO;
import cn.ac.nercis.pes.service.evaluation.SecurityThreatService;
import cn.ac.nercis.pes.service.evaluation.UnitRiskAnalysisService;
import javafx.application.Platform;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.*;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;

import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.ResourceBundle;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * 资产对象风险分析管理控制器
 * @author zpy
 * @version 1.0.0
 */
@Slf4j
public class AssetRiskAnalysisController implements Initializable, GlobalEventHandle {
    private static final String THREAT_CONCAT = "：";

    @FXML
    private StackPane page;
    @FXML
    private Button saveButton;
    @FXML
    private Button backButton;
    @FXML
    private Button lastButton;
    @FXML
    private Button nextButton;
    @FXML
    private Label titleLbl;

    @FXML
    private Label conclusionLbl;
    @FXML
    private HBox problemBlock;
    @FXML
    private VBox problemBox;
    @FXML
    private HBox measureBlock;
    @FXML
    private VBox measureBox;

    @FXML
    private ListView<MenuVO> menuList;

    @FXML
    private VBox problemEditBox;
    @FXML
    private VBox suggestEditBox;
    @FXML
    private VBox analysisEditBox;

    @FXML
    private TextArea problemTxt;
    @FXML
    private TextArea suggestTxt;
    @FXML
    private TextArea analysisTxt;
    @FXML
    private ToggleGroup riskLevelGroup;
    @FXML
    private RadioButton highLevelRbx;
    @FXML
    private RadioButton mediumLevelRbx;
    @FXML
    private RadioButton lowLevelRbx;
    @FXML
    private VBox threatBox;

    @FXML
    private CustomScrollPane editThreatPane;
    @FXML
    private VBox editThreatBox;

    private final UnitRiskAnalysisService unitRiskAnalysisService;
    private final SecurityThreatService securityThreatService;
    private final ExecutorService executor;
    protected final List<SecurityThreatDTO> threatList = new ArrayList<>();
    private final RealTimeSaveManager realTimeSaveManager = new RealTimeSaveManager(this::realTimeSave);

    private PageDialog pageDialog;
    private UnitRiskAnalysisDTO currentRiskAnalysis;
    private String currentSystemId;
    private List<UnitRiskAnalysisDTO> riskAnalysisList;
    private Integer currentRiskAnalysisIndex;


    public AssetRiskAnalysisController() {
        this.unitRiskAnalysisService = SpringUtils.getBean(UnitRiskAnalysisService.class);
        this.securityThreatService = SpringUtils.getBean(SecurityThreatService.class);
        this.executor = SpringUtils.getBean(ExecutorService.class);
    }

    @Override
    public void initialize(URL location, ResourceBundle resources) {
        saveButton.setGraphic(IconUtils.getGlyph("save-button-icon","btn-icon"));
        backButton.setGraphic(IconUtils.getGlyph("back-button-icon","btn-icon"));
        lastButton.setGraphic(IconUtils.getGlyph("left-icon","btn-icon"));
        nextButton.setGraphic(IconUtils.getGlyph("right-icon","btn-icon"));
        initializeMenu();
        lastButton.managedProperty().bind(lastButton.visibleProperty());
        nextButton.managedProperty().bind(nextButton.visibleProperty());
        problemBlock.managedProperty().bind(problemBlock.visibleProperty());
        measureBlock.managedProperty().bind(measureBlock.visibleProperty());
        problemEditBox.managedProperty().bind(problemEditBox.visibleProperty());
        suggestEditBox.managedProperty().bind(suggestEditBox.visibleProperty());
        analysisEditBox.managedProperty().bind(analysisEditBox.visibleProperty());
        GuiStore.applyQuickSave(this.page,this::onGlobalSave);
        realTimeSaveManager.listenerToggleGroupChange(riskLevelGroup);
        realTimeSaveManager.listenerTextChange(analysisTxt, problemTxt, suggestTxt);
    }

    public void initializeData(String systemId,UnitRiskAnalysisDTO riskAnalysisDTO,List<UnitRiskAnalysisDTO> riskAnalysisList){
        this.currentSystemId = systemId;
        this.riskAnalysisList = riskAnalysisList;
        if(this.riskAnalysisList.size() <= 1){
            this.lastButton.setVisible(false);
            this.nextButton.setVisible(false);
        }
        loadRiskAnalysisContent(riskAnalysisDTO);
        this.menuList.getSelectionModel().select(0);
    }

    private void initializeMenu(){
        this.menuList.setCellFactory(cf->new MenuListCell());
        this.menuList.getSelectionModel()
                .selectedItemProperty()
                .addListener((observable, oldValue, newValue) -> {
                    if(Objects.nonNull(newValue)){
                        loadContent(newValue);
                    }
                });
        this.menuList.getItems().add(new MenuVO(1,"problem","问题描述",""));
        this.menuList.getItems().add(new MenuVO(2,"suggest","整改建议",""));
        this.menuList.getItems().add(new MenuVO(3,"analysis","风险分析",""));
    }

    @Override
    public void onGlobalSave() {
        saveRiskAnalysis(false);
    }

    @FXML
    public void onLast(){
        this.gotoRiskAnalysis(false);
    }

    @FXML
    public void onNext(){
        this.gotoRiskAnalysis(true);
    }

    @FXML
    public void onBack(){
        var riskView = AppStore.getEvaluationView(this.currentSystemId, RiskAnalysisView.class);
        if(riskView.getController() instanceof RiskAnalysisController controller){
            controller.gotoRiskAnalysisList();
        }
    }

    @FXML
    public void onEditThreat(){
        pageDialog = PageDialog.builder()
                .withIcon("dialog-edit-icon")
                .withTitle("修改关联威胁")
                .withContent(this.editThreatPane)
                .withOwner(AppStore.getCurrentSystem(this.currentSystemId).getEvaluationStage())
                .build();
        pageDialog.setYesText("确定");
        pageDialog.setNoText("取消");
        pageDialog.show(this::saveEditThreat);
    }

    @FXML
    public void onFinishSave() {
        saveRiskAnalysis(false);
    }

    public void realTimeSave(){
        LogUtils.info(log, "执行即时保存操作...");
        saveRiskAnalysis(true);
    }

    private void saveRiskAnalysis(boolean realTime){
//        if (!realTime && !validContent()) {
//            return;
//        }
        collectionRiskAnalysis();
        CompletableFuture.supplyAsync(() -> unitRiskAnalysisService.saveRiskAnalysis(currentRiskAnalysis), this.executor)
                .thenAccept(res -> Platform.runLater(() -> {
                    if (res.isSuccess()) {
                        currentRiskAnalysis.setId(res.getData().getId());
                        if (!realTime) {
                            AppUtils.showMessageDialog("保存成功", MessageType.SUCCESS, this.page);
                        }
                    } else {
                        if (realTime) {
                            log.error("即时保存风险分析信息失败：{}",res.getMsg());
                        }else{
                            AppUtils.showMessageDialog(res.getMsg(),MessageType.FAIL,this.page);
                        }
                    }
                }))
                .exceptionally((ex) -> {
                    log.error("保存风险分析信息出错!", ex);
                    return null;
                });
    }

    @FXML
    public void onShowRecordButtonClick(){
        LogUtils.info(log, "执行查看记录详情操作...");
        var recordView = AppStore.getEvaluationView(this.currentSystemId, UnitRecordView.class);
        pageDialog = PageDialog.builder()
                .withTitle("测评记录")
                .withShowActions(false)
                .withContent(Objects.requireNonNull(recordView).load())
                .withOwner(AppStore.getCurrentSystem(this.currentSystemId).getEvaluationStage())
                .withOverlayClose(true)
                .build();
        if(recordView.getController() instanceof UnitRecordController controller){
            controller.initializeData(this.currentSystemId,currentRiskAnalysis.getUnitTargetId());
        }
        pageDialog.show();
    }


    private void loadContent(MenuVO menu){
        if(StringUtils.equals(menu.getCode(),"problem")){
            this.suggestEditBox.setVisible(false);
            this.analysisEditBox.setVisible(false);
            this.problemEditBox.setVisible(true);
        } else if (StringUtils.equals(menu.getCode(),"suggest")) {
            this.problemEditBox.setVisible(false);
            this.analysisEditBox.setVisible(false);
            this.suggestEditBox.setVisible(true);
        }else{
            this.problemEditBox.setVisible(false);
            this.suggestEditBox.setVisible(false);
            this.analysisEditBox.setVisible(true);
        }
    }

    private void loadRiskAnalysisContent(UnitRiskAnalysisDTO riskAnalysisDTO){
        this.currentRiskAnalysis = riskAnalysisDTO;
        this.currentRiskAnalysisIndex= 0;
        for(var item:riskAnalysisList){
            if(StringUtils.equals(item.getUnitTargetId(),riskAnalysisDTO.getUnitTargetId())){
                break;
            }
            this.currentRiskAnalysisIndex++;
        }
        this.lastButton.setDisable(this.currentRiskAnalysisIndex <= 0);
        this.nextButton.setDisable(this.currentRiskAnalysisIndex >= (riskAnalysisList.size()-1));
        ControlUtils.setLabelTextByLimit(titleLbl,currentRiskAnalysis.getUnitTargetName(), AppConstants.MAX_SHOW_TEXT_LENGTH);
        initContent();
    }

    private void gotoRiskAnalysis(boolean isNext) {
        log.info("执行切换当前风险分析操作...");
        //保存当前内容
//        if (!validContent()) {
//            return;
//        }
        collectionRiskAnalysis();
        CompletableFuture.supplyAsync(() -> unitRiskAnalysisService.saveRiskAnalysis(currentRiskAnalysis), this.executor)
                .thenAccept(res -> Platform.runLater(() -> {
                    if (res.isSuccess()) {
                        currentRiskAnalysis.setId(res.getData().getId());
                        int index = isNext ? this.currentRiskAnalysisIndex + 1 : this.currentRiskAnalysisIndex - 1;
                        if (index >= 0 && index < this.riskAnalysisList.size()) {
                            loadRiskAnalysisContent(this.riskAnalysisList.get(index));
                        }
                    } else {
                        MessageDialog.builder()
                                .withType(MessageType.FAIL).withMessage(res.getMsg())
                                .withContainer(this.page)
                                .build().show();
                    }
                }));
    }

    private void saveEditThreat(){
        var selectedCodes = findThreatCodeCheckbox().stream()
                .filter(CheckBox::isSelected)
                .map(CheckBox::getText)
                        .toList();
        currentRiskAnalysis.setThreatCode(StringUtils.join(selectedCodes,BaseConstants.MULTI_SPLIT_CHAR));
        this.renderThreat();
        this.setDefaultAnalysisContent(selectedCodes);
        this.realTimeSave();
        this.pageDialog.close();
    }

    private void setDefaultAnalysisContent(List<String> selectedThreatCodes){
        var selectThreats = this.threatList.stream()
                .filter(st->selectedThreatCodes.stream().anyMatch(code->StringUtils.equals(st.getCode(),code)))
                .toList();
        if(StringUtils.isNotBlank(currentRiskAnalysis.getAnalysis()) || CollectionUtils.isEmpty(selectThreats)){
            return;
        }
        int index = 0;
        StringBuilder defaultContent = new StringBuilder(String.format("存在%s",selectThreats.get(index++).getName()));
        for(;index<selectThreats.size();index++){
            defaultContent.append(String.format("，或%s",selectThreats.get(index).getName()));
        }
        defaultContent.append("的风险。");
        analysisTxt.setText(defaultContent.toString());
    }

    private void initContent() {
        realTimeSaveManager.setEnable(false);
        loadProblemMeasure();
        loadThreatList();
        this.conclusionLbl.setText(currentRiskAnalysis.getConclusion());
        problemTxt.setText(StringUtils.isEmpty(currentRiskAnalysis.getProblem()) ? "" : enterProcess(currentRiskAnalysis.getProblem()));
        suggestTxt.setText(StringUtils.isEmpty(currentRiskAnalysis.getSuggest()) ? "" : enterProcess(currentRiskAnalysis.getSuggest()));
        analysisTxt.setText(StringUtils.isEmpty(currentRiskAnalysis.getAnalysis()) ? "" : enterProcess(currentRiskAnalysis.getAnalysis()));
        RiskLevel currentLevel = RiskLevel.fromName(currentRiskAnalysis.getRiskLevel());
        highLevelRbx.setSelected(currentLevel == RiskLevel.HIGH);
        mediumLevelRbx.setSelected(currentLevel == RiskLevel.MEDIUM);
        lowLevelRbx.setSelected(currentLevel == RiskLevel.LOW);
        realTimeSaveManager.setEnable(true);
    }

    private void loadProblemMeasure(){
        CompletableFuture.supplyAsync(()->unitRiskAnalysisService.getRiskAnalysisProblemMeasure(currentRiskAnalysis.getSystemId(),currentRiskAnalysis.getUnitTargetId()),this.executor)
                .thenAccept(res-> Platform.runLater(()->{
                    if(res.isSuccess()){
                        initProblemMeasure(res.getData());
                    }else{
                        MessageDialog.builder()
                                .withType(MessageType.FAIL).withMessage(res.getMsg())
                                .build().show();
                        onBack();
                    }
                }));
    }

    private void initProblemMeasure(ProblemMeasureDTO problemMeasure){
        problemBox.getChildren().clear();
        if(StringUtils.isBlank(problemMeasure.getProblem())){
            problemBlock.setVisible(false);
        }else{
            problemBlock.setVisible(true);
            Stream.of(problemMeasure.getProblem().split(BaseConstants.REGEX_WRAP_TEXT_SPLIT))
                    .filter(StringUtils::isNotBlank)
                    .forEach(method->{
                        Label label = new Label(method);
                        label.setWrapText(true);
                        if(StringUtils.isNotBlank(method)){
                            label.setTooltip(new Tooltip(method));
                        }
                        problemBox.getChildren().add(label);
                    });
        }
        measureBox.getChildren().clear();
        if(StringUtils.isBlank(problemMeasure.getMeasure())){
            measureBlock.setVisible(false);
        }else{
            measureBlock.setVisible(true);
            Stream.of(problemMeasure.getMeasure().split(BaseConstants.REGEX_WRAP_TEXT_SPLIT))
                    .filter(StringUtils::isNotBlank)
                    .forEach(method->{
                        Label label = new Label(method);
                        label.setWrapText(true);
                        if(StringUtils.isNotBlank(method)){
                            label.setTooltip(new Tooltip(method));
                        }
                        measureBox.getChildren().add(label);
                    });
        }
    }

    private void loadThreatList(){
        CompletableFuture.supplyAsync(() -> securityThreatService.findSecurityThreats(currentRiskAnalysis.getSystemId()),this.executor)
                .thenAccept(res-> Platform.runLater(()->{
                    if(res.isSuccess()){
                        initThreatList(res.getData());
                    }else{
                        MessageDialog.builder()
                                .withType(MessageType.FAIL).withMessage(res.getMsg())
                                .build().show();
                        onBack();
                    }
                }));
    }

    private void initThreatList(List<SecurityThreatDTO> threats){
        threatList.clear();
        threatList.addAll(threats);
        renderThreat();
        editThreatBox.getChildren().clear();
        threatList.forEach(st->{
            HBox rowBox = new HBox();
            rowBox.getStyleClass().add("eval-common-row-box");
            HBox codeBox = new HBox();
            codeBox.getStyleClass().addAll("eval-common-label-box","edit-threat-code");
            codeBox.getChildren().add(new CheckBox(st.getCode()));
            rowBox.getChildren().add(codeBox);
            HBox textBox = new HBox();
            textBox.getStyleClass().add("eval-common-text-box");
            textBox.getChildren().add(new Label(st.getName()));
            HBox.setHgrow(textBox, Priority.SOMETIMES);
            rowBox.getChildren().add(textBox);
            editThreatBox.getChildren().add(rowBox);
        });
        refreshThreatSelected();
    }

    private void refreshThreatSelected(){
        String[] defaultCodes = StringUtils.split(currentRiskAnalysis.getThreatCode(),BaseConstants.MULTI_SPLIT_CHAR);
        if(Objects.isNull(defaultCodes) || defaultCodes.length == 0){
            return;
        }
        findThreatCodeCheckbox().forEach(cbx->
                cbx.setSelected(Stream.of(defaultCodes).anyMatch(tc->StringUtils.equals(tc,cbx.getText()))));
    }

    private List<CheckBox> findThreatCodeCheckbox(){
        return editThreatBox.getChildren().stream()
                .filter(node->node instanceof HBox)
                .map(node->(HBox)node)
                .flatMap(box->box.getChildren().stream())
                .filter(node->node instanceof HBox)
                .map(node->(HBox)node)
                .flatMap(box->box.getChildren().stream())
                .filter(node->node instanceof CheckBox)
                .map(node->(CheckBox)node)
                .collect(Collectors.toList());
    }

    private void renderThreat(){
        threatBox.getChildren().clear();
        if(StringUtils.isEmpty(currentRiskAnalysis.getThreatCode())){
            return;
        }
        Stream.of(StringUtils.split(currentRiskAnalysis.getThreatCode(),BaseConstants.MULTI_SPLIT_CHAR))
                .filter(StringUtils::isNotBlank)
                .forEach(threatCode-> threatList.stream()
                        .filter(st->StringUtils.equals(st.getCode(),threatCode))
                        .findFirst()
                        .ifPresent(st->{
                            Label label = new Label(st.getCode().concat(THREAT_CONCAT).concat(st.getName()));
                            label.setWrapText(true);
                            label.setTooltip(new Tooltip(label.getText()));
                            threatBox.getChildren().add(label);
                        }));
    }

    private String enterProcess(String value){
        if(StringUtils.contains(value,"\\n")){
            return StringUtils.replace(value,"\\n","\n");
        }else{
            return value;
        }
    }

    private boolean validContent(){
        if (StringUtils.isBlank(problemTxt.getText())) {
            MessageDialog.builder()
                    .withType(MessageType.WARN).withMessage("问题描述不允许为空！")
                    .withContainer(this.page)
                    .build().show();
            problemTxt.requestFocus();
            return false;
        }
        if (StringUtils.isBlank(suggestTxt.getText())) {
            MessageDialog.builder()
                    .withType(MessageType.WARN).withMessage("整改建议不允许为空！")
                    .withContainer(this.page)
                    .build().show();
            suggestTxt.requestFocus();
            return false;
        }
        if (StringUtils.isBlank(analysisTxt.getText())) {
            MessageDialog.builder()
                    .withType(MessageType.WARN).withMessage("风险分析不允许为空！")
                    .withContainer(this.page)
                    .build().show();
            analysisTxt.requestFocus();
            return false;
        }
        if (StringUtils.isEmpty(currentRiskAnalysis.getThreatCode())) {
            onEditThreat();
            MessageDialog.builder()
                    .withType(MessageType.WARN).withMessage("请选择关联威胁！")
                    .withContainer(this.page)
                    .build().show();
            return false;
        }
        if (Objects.isNull(riskLevelGroup.getSelectedToggle())) {
            MessageDialog.builder()
                    .withType(MessageType.WARN).withMessage("请选择风险等级！")
                    .withContainer(this.page)
                    .build().show();
            return false;
        }
        return true;
    }

    private void collectionRiskAnalysis() {
        currentRiskAnalysis.setProblem(problemTxt.getText());
        currentRiskAnalysis.setSuggest(suggestTxt.getText());
        currentRiskAnalysis.setAnalysis(analysisTxt.getText());
        RadioButton currentRbx = (RadioButton) riskLevelGroup.getSelectedToggle();
        if (Objects.nonNull(currentRbx)) {
            currentRiskAnalysis.setRiskLevel(RiskLevel.fromName(currentRbx.getText()).getName());
        }
    }
}